package net.towee.client.com.repo.presenter;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import net.towee.client.ToweeGinjector;
import net.towee.client.com.app.AppController.Component;
import net.towee.client.com.repo.AbstractNodeShower;
import net.towee.client.com.repo.RepositoryDocumentEditor;
import net.towee.client.com.repo.RepositoryNodeCreator;
import net.towee.client.com.repo.RepositoryShower;
import net.towee.client.event.core.UserLeaveFromGroupEvent;
import net.towee.client.event.core.UserLeaveFromGroupHandler;
import net.towee.client.history.HistoryEvent;
import net.towee.client.history.HistoryHandler;
import net.towee.client.history.HistoryManager;
import net.towee.client.managers.AccountHelper;
import net.towee.client.managers.DocumentsManager;
import net.towee.client.managers.NodesManager;
import net.towee.model.group.GroupIdentity;
import net.towee.model.repo.DocumentData;
import net.towee.model.repo.DocumentType;
import net.towee.model.repo.NodeData;
import net.towee.model.repo.impl.NodeDataImpl;

import com.google.common.base.Preconditions;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.HasWidgets;
import com.google.gwt.user.client.ui.IsWidget;
import com.google.gwt.user.client.ui.Widget;
import com.google.inject.Inject;

public class RepositoryShowerPresenter implements RepositoryShower, HistoryHandler {

	public interface Display extends IsWidget {
		/**
		 * @return List of buttons for create some kind of documents. It can be
		 *         casted to Widget and contain in Layout type of document
		 *         {@link DocumentType}
		 */
		List<HasClickHandlers> getAddDocumentButtons();

		/**
		 * Button for removing active node
		 */
		HasClickHandlers getRemoveNodeButton();

		/**
		 * @return Button for cutting nodes
		 */
		HasClickHandlers getCutButton();

		/**
		 * @return Button for paste nodes. By default this button hidden. It
		 *         will be showed when user click cut button.
		 */
		HasClickHandlers getPasteButton();

		/**
		 * @return button for creating new tree node in current showed node.
		 */
		HasClickHandlers getAddNodeButton();

		/**
		 * Invoked when user click cut button
		 */
		void enablePasteButton();

		void disablePasteButton();

		/**
		 * Show buttons
		 */
		void showButtons();

		/**
		 * @return container for showers
		 */
		HasWidgets getShowerContainer();

		void hideButtons();
	}

	// Injected
	private final Display display;
	private final AccountHelper account;
	private final ToweeGinjector injector;
	private final NodesManager nodesManager;
	private final HistoryManager history;
	private final DocumentsManager documentsManager;
	private final HandlerManager eventBus;

	// Container of shower
	private HasWidgets container;

	// Current showed node
	private NodeData currentNode = null;
	private AbstractNodeShower shower = null;
	private List<NodeData> cutNodes = null;
	private RepositoryDocumentEditor editor = null;

	@Inject
	public RepositoryShowerPresenter(Display display, AccountHelper account, ToweeGinjector injector,
			NodesManager nodesManager, HistoryManager history, DocumentsManager docsManager,
			HandlerManager eventBus) {
		this.display = display;
		this.account = account;
		this.injector = injector;
		this.nodesManager = nodesManager;
		this.documentsManager = docsManager;
		this.history = history;
		this.eventBus = eventBus;

		bindButtons();
	}

	private void bindButtons() {
		// Create handler for add buttons
		ClickHandler addDocHandler = new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				Object source = event.getSource();

				// Check source
				Preconditions.checkArgument((source instanceof Widget), "source is not widget");
				Preconditions.checkArgument(((Widget) source).getLayoutData() instanceof DocumentType,
						"widget not contain document type");

				// Get type of document for creating
				DocumentType type = (DocumentType) ((Widget) source).getLayoutData();
				
				// Add history event
				history.addHistoryItem(Component.REPO, "group", currentNode.getGroupId(), "node", currentNode.getId(), "create", type.toString().toLowerCase());

				// Show creator
				showDocumentCreator(type);
			}
		};

		// Add handler to buttons
		for (HasClickHandlers b : display.getAddDocumentButtons()) {
			b.addClickHandler(addDocHandler);
		}

		// Set handler for cut button
		display.getCutButton().addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				cutNodes();
			}
		});

		// Set handler for paste button
		display.getPasteButton().addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				pasteNodes();
			}
		});

		// Set handler for nodes removing
		display.getRemoveNodeButton().addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				removeNodes();
			}
		});

		// Set handler for node creator
		display.getAddNodeButton().addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				// Get creator and set ids
				RepositoryNodeCreator creator = injector.getRepositoryNodeCreator();
				creator.setParentNode(currentNode);

				// Shoe creator
				creator.go(container);
			}
		});
		
		eventBus.addHandler(UserLeaveFromGroupEvent.TYPE, new UserLeaveFromGroupHandler() {
			@Override
			public void onUserLeaveGroup(GroupIdentity identity) {
				if( currentNode !=null && identity.getId().equals( currentNode.getGroupId() )) {
					display.getShowerContainer().clear();
					display.hideButtons();
					currentNode = null;
				}
			}
		});

		history.addHandler(Component.REPO, this);
	}

	private void showDocumentCreator(DocumentType type) {
		// Show editro
		if (editor != null) {
			editor.setDocumentType(type);
			editor.go(display.getShowerContainer());
		} else {
			// Get document creator
			editor = injector.getRepositoryDocumentEditor();
			editor.setEmptyDocument(type, currentNode, account.getAccount().getAccountIdentity());
			editor.go(display.getShowerContainer());
		}
	}

	/**
	 * Invoked when user press CUT button
	 */
	private void cutNodes() {
		Preconditions.checkNotNull(shower, "null shower");

		// Enable paste button
		display.enablePasteButton();

		// Save cut nodes
		cutNodes = shower.getSelectedNodes();

		// Hide nodes
		shower.removeNodes(cutNodes);
	}

	/**
	 * Invoked when user press PASTE button
	 */
	private void pasteNodes() {
		Preconditions.checkNotNull(cutNodes, "null cut nodes");

		// Disable paste button
		display.disablePasteButton();

		if (currentNode.getId().equals(cutNodes.get(0).getParentId()))
			shower.showNodes(cutNodes);
		else {
			// Create list with ids
			List<String> nodeIds = new ArrayList<String>();
			for (NodeData n : cutNodes)
				nodeIds.add(n.getId());

			// Move nodes
			nodesManager.moveNodes(null, currentNode.getId(), nodeIds, new AsyncCallback<Boolean>() {
				@Override
				public void onFailure(Throwable caught) {
				}

				@Override
				public void onSuccess(Boolean result) {
					shower.showNodes(cutNodes);
				}
			});
		}
	}

	/**
	 * Invoked when user press REMOVE button
	 */
	private void removeNodes() {
		// TODO: add removing nodes
	}

	@Override
	public void go(HasWidgets container) {
		// Set container
		this.container = container;

		// Clear container and display shower
		container.clear();
		container.add(display.asWidget());
	}

	@Override
	public void showTreeNode(final NodeData node) {
		showTreeNode(node, true);
	}

	private void showTreeNode(final NodeData node, boolean doHistory) {
		// Remove any editor
		editor = null;

		// Add new history item
		if (doHistory == true) {
			if (node.getId() == null)
				history.addHistoryItem(Component.REPO, "group", node.getGroupId());
			else
				history.addHistoryItem(Component.REPO, "group", node.getGroupId(), "node", node.getId());
		}

		// Show buttons if it's first start
		if (currentNode == null)
			display.showButtons();

		// If node is TreeNode
		if (node.toTreeNode() != null) {
			// Set active node
			this.currentNode = node;

			// Create shower
			shower = injector.getRepositoryTreeNodeShower();

			// Set node and display shower
			shower.setNode(currentNode);
			shower.go(display.getShowerContainer());
		}
		// If document node
		else {
			nodesManager.getNode(node.getParentId(), node.getGroupId(), new AsyncCallback<NodeData>() {
				@Override
				public void onFailure(Throwable caught) {
				}

				@Override
				public void onSuccess(NodeData result) {
					// Set active node
					currentNode = result;
					shower = injector.getRepositoryDocumentShower();

					// Set node and display shower
					shower.setNode(node);
					shower.go(display.getShowerContainer());
				}
			});
		}
	}

	/**
	 * Hell as is %)
	 */
	@Override
	public void onHistoryEvent(HistoryEvent event) {
		final Map<String, String> params = event.getParameters();

		if (params.containsKey("group")) {
			String groupId = event.getParameters().get("group");

			if (account.getAccountGroup(groupId) != null) {
				// Show buttons
				display.showButtons();
				
				// Create root node
				GroupIdentity group = account.getAccountGroup(groupId).getGroupIdentity();

				// Show root of group
				if (!params.containsKey("node")) {
					NodeData node = new NodeDataImpl(null, group.getId(), group.getPrintName());
					
					// Show node
					showTreeNode(node, false);
				}
				// Show node
				else {
					// If it's group node
					if( params.get("node").equals("null") ) { 
						currentNode = new NodeDataImpl(null, group.getId(), group.getPrintName());
						maybeCreateEvent(params);
					}
					// Show editor or node
					else {
						nodesManager.getNode(params.get("node"), groupId, new AsyncCallback<NodeData>() {
							@Override
							public void onFailure(Throwable caught) {
							}

							@Override
							public void onSuccess(final NodeData docNode) {
								currentNode = docNode;
								maybeCreateEvent(params);
							}
						});
					}
				}
			}
		}
	}
	
	private void maybeCreateEvent(Map<String, String> params) {
		// Show document creator
		if (params.containsKey("create")) {
			DocumentType type = DocumentType.valueOf(params.get("create").toUpperCase());

			if (type != null)
				showDocumentCreator(type);
		}
		// If it's edit event and node is document
		else if (params.containsKey("edit") && currentNode.toDocumentNode() != null) {
			nodesManager.getNode(currentNode.getParentId(), currentNode.getGroupId(),
				new AsyncCallback<NodeData>() {
				@Override
				public void onFailure(Throwable caught) {
				}

				@Override
				public void onSuccess(final NodeData result) {
					documentsManager.getDocument(currentNode.toDocumentNode()
							.getDocumentIdentity().getId(),
					new AsyncCallback<DocumentData>() {
						@Override
						public void onFailure(Throwable caught) {
						}

						@Override
						public void onSuccess(DocumentData doc) {
							// Create editor
							RepositoryDocumentEditor editor = injector
									.getRepositoryDocumentEditor();
							editor.setDocument(doc, result);

							// Show editor
							editor.go( display.getShowerContainer() );				
						}
					});
					
					currentNode = result;
				}
			});
		}
		// If it's not edit event or node not a document
		else {
			showTreeNode(currentNode, false);
		}
	}

}
